/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                         *
 *   AndroidWorld Library, Copyright 2011 Bryan Chadwick                   *
 *                                                                         *
 *   FILE: ./android/world/test/ConnectFourGame.java                       *
 *                                                                         *
 *   This file is part of AndroidWorld.                                    *
 *                                                                         *
 *   AndroidWorld is free software: you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License           *
 *   as published by the Free Software Foundation, either version          *
 *   3 of the License, or (at your option) any later version.              *
 *                                                                         *
 *   AndroidWorld is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with AndroidWorld.  If not, see <http://www.gnu.org/licenses/>. *
 *                                                                         *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

package android.world.test;

import java.util.*;

import android.os.Bundle;
import android.view.Display;
import android.world.*;
import android.app.Activity;
import android.image.*;

public class ConnectFourGame extends Activity{
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        Display dis = getWindowManager().getDefaultDisplay();
        new ConnectFour(dis.getWidth(), dis.getHeight()-50).bigBangLandscape(this);
    }
}

/** Represents the State of the game
 *  - This is an alternative to the other examples, where we
 *      track the state, and react to *it*, rather than having
 *      the state classes encapsulate the reactions themselves.
 */
abstract class State{
    /** What happens when someone wins */
    State win(){ return this; }
    /** What happens when the game is restarted */
    State restart(ConnectFour game){ return this; }
    /** Are we in the "Playing" state? (not the Losing State) */
    boolean isPlaying(){ return false; }
}
/** Represents the normal game play State */
class Play extends State{
    /** When someone wins, we enter the Lose State */
    State win(){ return new Lose(); }
    /** Yes... we are Playing */
    boolean isPlaying(){ return true; }
}

/**  */
class Lose extends State{
    /** When we restart, we restart and enter the Play State */
    State restart(ConnectFour game){
        game.restart();
        return new Play();
    }
}

/** Represents a Player's Token */
class Token{
    String color;

    Token(String color) {
        this.color = color;
    }
}

/** Represents the <blink>Connect Four</blink> <marquee>Game / World</marquee> */
class ConnectFour extends VoidWorld{
    static int G_WIDTH = 7;
    static int G_HEIGHT = 6;

    State state;
    
    /** Convert a grid number to screen X */
    int grid2ScreenX(int gx){
        return gx*this.SIZE+this.SIZE/2+(this.S_WIDTH-(G_WIDTH*this.SIZE))/2;
    }

    /** Convert a grid number to screen Y */
    int grid2ScreenY(int gy){
        return this.S_HEIGHT-(this.S_HEIGHT-(G_HEIGHT*this.SIZE))/2-
               (gy*this.SIZE+this.SIZE/2);
    }

    /** Screen/Scene Size and the width of a Grid/Piece
     * I changed them to be fields (i.e., not static) so that we can
     *    move the game to Android easily. */
    int S_WIDTH, S_HEIGHT;
    int SIZE;
    /** Basic Background... */
    Scene BACK;
    boolean who = true;

    /** One possible representation of the current state of our game */
    ConnectRep chips;

    ConnectFour(int W, int H){
        this(W, H,new Board(ConnectFour.G_WIDTH));
    }
    ConnectFour(int W, int H, ConnectRep chips){
        this.S_WIDTH = W;
        this.S_HEIGHT = H;
        this.SIZE = Math.min(W, H)/Math.max(G_WIDTH, G_HEIGHT);
        this.chips = chips;
        this.BACK = new EmptyScene(this.S_WIDTH, this.S_HEIGHT)
                       .placeImage(new Rectangle(this.S_WIDTH, this.S_HEIGHT, "solid", "gold"),
                               this.S_WIDTH/2, this.S_HEIGHT/2)
                       .placeImage(new Rectangle(this.S_WIDTH, this.SIZE/8, "solid", "blue"),
                               this.S_WIDTH/2, this.S_HEIGHT-this.SIZE/16);
        
        this.state = new Play();
    }

    /** Slow the tick rate to enhance overall performance */
    public double tickRate(){
        return 1.0;
    }
    
    /** NEW: Restart the game */
    void restart(){
        this.chips = new Board(ConnectFour.G_WIDTH);
        this.who = true;
    }
    /** Add a Chip on Mouse Click */
    public void onMouse(int x, int y, String me){
        // NEW: playing determines action... 
        if(this.state.isPlaying() && me.equals("button-down")){
            this.chips.addToken((x-(this.S_WIDTH-(G_WIDTH*this.SIZE))/2)/this.SIZE, currentPlayer());
            if(checkWin(currentPlayer()))
                this.state = this.state.win();
            this.who = !this.who;
        }else{
            // NEW: Get ready for Android!!!  Restart the game on a long press
            if(me.equals("long-button-down"))
                this.state = this.state.restart(this);
        }
    }

    public void onKey(String key){
        // NEW: playing determines action... 
        if(!this.state.isPlaying()){
            if(key.equals("\n"))
                this.state = this.state.restart(this);
        }
    }
    
    /** Draw all the tokens in the board */
    public Scene onDraw(){
        // NEW: playing determines action...
        if(this.state.isPlaying())
            return drawchips(0, 0, this.BACK);
        else
            return lastScene();
    }
    /** Draw everything */
    Scene drawchips(int gx, int gy, Scene scn){
        if(gy >= G_HEIGHT){
            return scn;
        }else{
            if(gx >= G_WIDTH){
                return drawchips(0, gy+1, scn);
            }else{
                return drawchips(gx+1, gy, 
                        scn.placeImage(new Circle(this.SIZE/2-this.SIZE/8, "solid", this.chips.getOwner(gx, gy)),
                                this.grid2ScreenX(gx), this.grid2ScreenY(gy)));
            }
        }
    }

    /** See if the player won */
    boolean checkWin(String player){
        for(int row = 0; row < G_HEIGHT; row++){
            for(int col = 0; col < G_WIDTH; col++){
                if(this.chips.getOwner(col, row).equals(player) &&
                        (sameStrs(col, row, col, row+1,
                                col, row+2, col, row+3) ||
                                sameStrs(col, row, col+1, row,
                                        col+2, row, col+3, row) ||
                                        sameStrs(col, row, col+1, row+1,
                                                col+2, row+2, col+3, row+3) ||
                                                sameStrs(col, row, col+1, row-1,
                                                        col+2, row-2, col+3, row-3))){
                    return true;
                }
            }   
        }
        return false;
    }

    /** Are all the "owners" of the indices the same */
    boolean sameStrs(int c1, int r1, int c2, int r2,
            int c3, int r3, int c4, int r4){
        return (this.chips.getOwner(c1, r1).equals(this.chips.getOwner(c2, r2)) &&
                this.chips.getOwner(c2, r2).equals(this.chips.getOwner(c3, r3)) &&
                this.chips.getOwner(c3, r3).equals(this.chips.getOwner(c4, r4)));
    }
    /** Who's the player waiting to play */
    String currentPlayer(){
        if(this.who)return "red";
        return "black";
    }
    /** Who's the player who just went */
    String previousPlayer(){
        if(this.who)return "black";
        return "red";
    }
    /** Draw an exciting message at the end of the game!! */
    public Scene lastScene(){
        return this.addMessage("You Won "+ this.previousPlayer().toUpperCase(),
                   this.S_WIDTH/2+2, this.S_HEIGHT/2+2, this.previousPlayer(),
                   this.addMessage("Press ENTER to play again!",
                       this.S_WIDTH/2, this.S_HEIGHT/2+40, this.previousPlayer(),
                       this.drawchips(0, 0, this.BACK)));
    }
    /** Add a message, so that we can make it readable. */
    public Scene addMessage(String s, int x, int y, String color, Scene scn){
        return scn.placeImage(new Text(s, this.S_WIDTH/20, "darkgray"), x+2, y+2)
                  .placeImage(new Text(s, this.S_WIDTH/20, color), x, y);
    }
}

/** Represents a Connect Four Board */
interface ConnectRep{
    /** Get the owner of the given square/space/spot */
    String getOwner(int col, int row);
    /** Add a token to the given column with the given Owner */
    void addToken(int col, String owner);
}


/** Johns representation of the Connect Four Board */
class Board implements ConnectRep{
    ArrayList<ArrayList<Token>> board;

    Board(int cols){
        // Need ArrayList<...>
        this.board = new ArrayList<ArrayList<Token>>();
        this.loopItUp(cols, this.board);
    }
    /** Add all the necessary columns to the ArrayList */
    void loopItUp(int c, ArrayList<ArrayList<Token>> lst){
        if(c > 0){
            lst.add(new ArrayList<Token>());
            this.loopItUp(c-1, lst);
        }
    }
    /** Get the owner of the given square/space/spot */
    public String getOwner(int col, int row){
        if(this.board.size() > col && col >= 0 &&
                this.board.get(col).size() > row && row >= 0){
            // Has Owner
            return this.board.get(col).get(row).color;
        }else{
            // No Owner
            return "white";
        }
    }
    /** Add a token to the given column with the given Owner */
    public void addToken(int col, String owner){
        if(this.board.get(col).size() < ConnectFour.G_HEIGHT){
            // Not Full...
            this.board.get(col).add(new Token(owner));
        }
    }    
}
